﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;

namespace Framework.Reflection
{
    public static class TypeLibrary
    {
        static TypeLibrary()
        {
            Refresh();
        }

        static List<Desc> Internal = new List<Desc>();
        static List<Assembly> KnownAssemblies = new List<Assembly>();
        static object locker = new object();

        public static IEnumerable<Type> Matching(this Type t, Func<Type, bool> where)
        {
            return Internal.AsParallel()
                .Where(
                    x =>
                        x.Attributes.Contains(t)
                        || (t.IsInterface && x.Interfaces.Contains(t))
                        || (t.IsClass && x.Base.Contains(t))
                        || (t.IsGenericTypeDefinition && x.Type.IsGenericType && x.Type.GetGenericTypeDefinition() == t)
                        || (t.IsGenericTypeDefinition && x.Base.Any(b => b.IsGenericType && b.GetGenericTypeDefinition() == t))
                        || (t.IsGenericTypeDefinition && x.Interfaces.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == t))
                    )
                .Select(x => x.Type)
                .Where(where);
        }

        public static IEnumerable<Type> Matching(this Type t)
        {
            return t.Matching(x => true);
        }

        public static IEnumerable<Desc> GetDescriptions(Func<Type, bool> where)
        {
            return Internal.Where(x => where(x.Type));
        }

        public static Type GetType(string typeName)
        {
            System.Type type = System.Type.GetType(typeName);
            if (type == null) Internal.AsParallel().FirstOrDefault(x => x.Type.FullName == typeName);
            return type;
        }

       public class Desc
        {
            public Type Type { get; set; }
            public Type[] Interfaces { get; set; }
            public Type[] Base { get; set; }
            public Type[] Attributes { get; set; }

            public override string ToString()
            {
                return Type.ToString();
            }
        }

        static void Refresh()
        {
            lock (locker)
            {
                Console.WriteLine("Refreshing TypeLibrary");
                KnownAssemblies = AppDomain.CurrentDomain
                        .GetAssemblies()
                        .ToList();

                Internal =
                        KnownAssemblies
                        .SelectMany(x => GetMappings(x))
                        .ToList();
            }
        }

        public static void Enrich(string directoryPath, bool includeSubDirectories)
        {
            if (directoryPath == null) throw new ArgumentNullException("Unable to enrich TypeLibrary. Provided 'directoryPath' argument is null");
            var directoryInfo = new DirectoryInfo(directoryPath);
            if (!directoryInfo.Exists) throw new ApplicationException(String.Format("Unable to enrich TypeLibrary. Directory {0} does not exists", directoryPath));
            var searchOption = includeSubDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;

            var files = directoryInfo
                .GetFiles("*.dll", searchOption)
                .Select(y => new { Path = y.FullName, Name = Path.GetFileNameWithoutExtension(y.Name) });

            bool reloadingRequired = false;
            foreach (var file in files)
            {
                try
                {
                    var assembly = Assembly.LoadFrom(file.Path);
                    if (!reloadingRequired && !KnownAssemblies.Contains(assembly)) reloadingRequired = true;
                }
                catch (BadImageFormatException)
                {
                    Console.WriteLine("BadImageFormatException :" + file.Path);
                }
                catch (FileLoadException)
                {
                    Console.WriteLine("FileLoadException :" + file.Path);
                }
            }
            if (reloadingRequired) Refresh();
        }

        static IEnumerable<Desc> GetMappings(Assembly assembly)
        {
            return assembly.GetTypes()
                .Select(x => new Desc
                {
                    Type = x,
                    Interfaces = x.GetInterfaces(),
                    Base = GetParents(x),
                    Attributes = x.GetCustomAttributes(true).Select(a => a.GetType()).ToArray()
                });
        }

        static Type[] GetParents(Type t)
        {
            List<Type> res = new List<Type>();
            if (t.BaseType != null && t.BaseType != typeof(object))
            {
                res.Add(t.BaseType);
                res.AddRange(GetParents(t.BaseType));
            }

            return res.ToArray();
        }
    }
}
